// This runs the eval tests located in the testdata folder.
//
// There are several kinds of eval tests:
//
//   - tests in nix_tests are from C++ Nix, tests in tvix_tests are from
//     the Tvix project
//
//   - tests are either named eval-okay-* (evaluation should succeed),
//     or eval-fail-* (evaluation should fail)
//
//   - for most eval-okay-* tests, an expected output is present in the
//     corresponding .exp file
//
//   - tests that are expected to fail (because the features they
//     require are not yet implemented) are stored in a `notyetpassing`
//     subdirectory.
package main

import (
	"embed"
	"fmt"
	"io/fs"
	"log"
	"os"
	"strings"

	"tvl.fyi/tvix/eval"
)

//go:embed testdata
var testdata embed.FS

var passed, failed int

func red(text string, args ...any) string {
	f := "\033[31m" + text + "\033[0m"
	return fmt.Sprintf(f, args)
}

func green(text string, args ...any) string {
	f := "\033[32m" + text + "\033[0m"
	return fmt.Sprintf(f, args)
}

func runEvalFailTest(input string) {
	name := strings.TrimPrefix(strings.TrimSuffix(input, ".nix"), "testdata/")

	defer func() {
		if p := recover(); p != nil {
			fmt.Println(red("%s: PANIC:", name), p)
			failed++
		}
	}()

	test, err := fs.ReadFile(testdata, input)
	if err != nil {
		fmt.Println(red("%s: failed to read input from %s: %s", name, input, err))
		os.Exit(1)
	}

	_, err = eval.EvalNix(string(test), false)

	// eval-fail-* tests fail if there is *no* error:
	if err == nil {
		fmt.Println(red("%s: FAIL", name))
		failed++
	} else {
		fmt.Println(green("%s: PASS", name))
		passed++
	}
}

func TestEvalFailTests() {
	tests, err := fs.Glob(testdata, "testdata/*_tests/eval-fail-*.nix")
	if err != nil {
		fmt.Println("failed to list eval-fail tests:", err)
		os.Exit(1)
	}

	for _, test := range tests {
		runEvalFailTest(test)
	}
}

func runEvalOkayTest(input string) {
	name := strings.TrimPrefix(strings.TrimSuffix(input, ".nix"), "testdata/")

	defer func() {
		if p := recover(); p != nil {
			fmt.Println(red("%s: PANIC:", name), p)
			failed++
		}
	}()

	test, err := fs.ReadFile(testdata, input)
	if err != nil {
		log.Fatalf("failed to read input from %s: %s", input, err)
	}

	exp, err := fs.ReadFile(testdata, strings.TrimSuffix(input, ".nix")+".exp")
	if err != nil {
		fmt.Println(red("%s: FAIL:", name), "could not read test expectation:", err)
		failed++
		return
	}

	value, err := eval.EvalNix(string(test), true)
	if err != nil {
		fmt.Println(red("%s: FAIL:", name), err)
		failed++
		return
	}

	exps := strings.TrimSpace(string(exp))
	vals := eval.Display(value)
	if exps != vals {
		fmt.Println(red("%s: FAIL:", name), fmt.Sprintf("expected result %q, but got %q", exps, vals))
		failed++
	} else {
		fmt.Println(green("%s: PASS", name))
		passed++
	}
}

func TestEvalOkayTests() {
	tests, err := fs.Glob(testdata, "testdata/*_tests/eval-okay-*.nix")
	if err != nil {
		log.Fatal("failed to list eval-okay tests:", err)
	}

	for _, test := range tests {
		runEvalOkayTest(test)
	}
}

func main() {
	TestEvalFailTests()
	TestEvalOkayTests()

	fmt.Println(green("%d PASSED", passed), "/", red("%d FAILED", failed))
	if failed != 0 {
		os.Exit(1)
	}
}
